home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / MiscKit1.7.1 / MiscKit / Source / MiscKit / MiscSubprocess.m < prev    next >
Encoding:
Text File  |  1995-07-21  |  18.2 KB  |  650 lines

  1. //
  2. //    MiscSubprocess.m -- a Obj-C wrapper around Unix child processes
  3. //        Originally written by Drew Davidson.
  4. //        Copyright (c) 1994 by Drew Davidson.
  5. //        Modified by Don Yacktman for inclusion into the MiscKit.
  6. //        Fixed up by Carl Lindberg, Don Yacktman, and Steve Hayman.
  7. //                Version 1.3.  All rights reserved.
  8. //        This notice may not be removed from this source code.
  9. //
  10. //    This object is included in the MiscKit by permission from the author
  11. //    and its use is governed by the MiscKit license, found in the file
  12. //    "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
  13. //    for a list of all applicable permissions and restrictions.
  14. //    
  15.  
  16. /*----------------------------------------------------------------------------
  17.     $Source$
  18.  
  19.     SYNOPSIS
  20.         Handles a subprocess that is fork()'ed and handles delegate
  21.         notification of events that occur such as output and errors.
  22.  
  23.     From Subprocess example by Charles L. Oei
  24.             pty support (removed) by Joe Freeman
  25.             with encouragement from Kristofer Younger
  26.             Subprocess Example, Release 2.0
  27.             NeXT Computer, Inc.
  28.                                     
  29.     Modified to support signals SIGSTOP and SIGCONT, and to wait for error
  30.     code from terminated process, and to add delegate methods for the output by
  31.     Drew Davidson
  32.     
  33.     Modified for MiscKit inclusion by Don Yacktman
  34.     Debugged by Carl Lindberg, Don Yacktman, Steve Hayman
  35.     PTY support added back in by Carl Lindberg
  36.     Synchronous operation by Steve Hayman
  37.  
  38.     REVISIONS
  39.     $Log$
  40. ----------------------------------------------------------------------------*/
  41. #import <libc.h>
  42. #import <misckit/MiscString.h>
  43. #import <misckit/MiscStringArray.h>
  44. #import <misckit/MiscSubprocess.h>
  45.  
  46. extern int    wait4(int, union wait *, int, struct rusage *);
  47. static void    stdoutFdHandler(int theFd, id self);
  48. static void    stderrFdHandler(int theFd, id self);
  49.  
  50. #define PIPE_ERROR    "Error starting UNIX pipes to subprocess."
  51. #define VFORK_ERROR    "Error starting UNIX vfork of subprocess."
  52. #define EXEC_ERROR    "Error starting UNIX exec of subprocess."
  53. #define    PTY_TEMPLATE "/dev/pty??"
  54. #define    PTY_LENGTH 11
  55.  
  56. @interface MiscSubprocess(private)
  57.  
  58. - _childDidExit;
  59. - _stdoutBuffer;
  60. - _stderrBuffer;
  61. - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString;
  62. - _setRunning:(BOOL)yn;
  63.  
  64. @end
  65.  
  66. static void
  67. getptys (int *master, int *slave)
  68.     // attempt to setup the ptys
  69. {
  70.     char device[PTY_LENGTH];
  71.     char *block, *num;
  72.     char *blockLoc; // specifies the location of block for the device string
  73.     char *numLoc; // specifies the pty name with the digit ptyxD
  74.     char *msLoc; // specifies the master (ptyxx) or slave (ttyxx)
  75.     
  76.     struct sgttyb setp =
  77.     {B9600, B9600, (char)0x7f, (char)0x15, (CRMOD|ANYP)};
  78.     struct tchars setc =
  79.     {CINTR, CQUIT, CSTART, CSTOP, CEOF, CBRK};
  80.     struct ltchars sltc =
  81.     {CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT};
  82.     int    lset =
  83.     (LCRTBS|LCRTERA|LCRTKIL|LCTLECH|LPENDIN|LDECCTQ);
  84.     int    setd = NTTYDISC;
  85.     
  86.     strcpy(device, PTY_TEMPLATE); // string constants are not writable
  87.     blockLoc = &device[ strlen("/dev/pty") ];
  88.     numLoc = &device[ strlen("/dev/pty?") ];
  89.     msLoc = &device[ strlen("/dev/") ];
  90.     for (block = "pqrs"; *block; block++)
  91.     {
  92.     *blockLoc = *block;
  93.     for (num = "0123456789abcdef"; *num; num++)
  94.     {
  95.         *numLoc = *num;
  96.         *master = open(device, O_RDWR);
  97.         if (*master >= 0)
  98.         {
  99.         *msLoc = 't';
  100.         *slave = open(device, O_RDWR);
  101.         if (*slave >= 0)
  102.         {
  103.             (void) ioctl(*slave, TIOCSETP, (char *)&setp);
  104.             (void) ioctl(*slave, TIOCSETC, (char *)&setc);
  105.             (void) ioctl(*slave, TIOCSETD, (char *)&setd);
  106.             (void) ioctl(*slave, TIOCSLTC, (char *)&sltc);
  107.             (void) ioctl(*slave, TIOCLSET, (char *)&lset);
  108.             return;
  109.         }
  110.         //  Change it back if the open  of the "slave" of the pty fails.
  111.         *msLoc = 'p';    // to cause it to open the master end of pty
  112.                 // the next time through the loop.
  113.         }
  114.     } /* hunting through a bank of ptys */
  115.     } /* hunting through blocks of ptys in all the right places */
  116.     *master = -1;
  117.     *slave = -1;
  118. }
  119.  
  120.  
  121. @implementation MiscSubprocess
  122.  
  123. /*----------------------------< PRIVATE METHODS >----------------------------*/
  124. /*
  125.  * cleanup after a child process exits
  126.  */
  127. - _childDidExit
  128. {
  129.     union wait w; int status = 0;
  130.     MiscSubprocessEndCode code = Misc_UnknownEndCode;
  131.  
  132.     if (wait4(childPid,&w,WUNTRACED,NULL) > 0) {
  133.         IMP done;
  134.         if (WIFEXITED(w)) {
  135.             code = Misc_Exited;
  136.             status = w.w_retcode;
  137.         } else {
  138.             if (WIFSTOPPED(w)) {
  139.                 code = Misc_Stopped;
  140.                 status = w.w_stopsig;
  141.             } else {
  142.                 if (WIFSIGNALED(w)) {
  143.                     code = Misc_Signaled;
  144.                     status = w.w_termsig;
  145.                 }
  146.             }
  147.         }
  148.         
  149.         if ( dpsStdoutFromChild >= 0 )
  150.             DPSRemoveFD(dpsStdoutFromChild);
  151.         if ( dpsStderrFromChild >= 0 )
  152.             DPSRemoveFD(dpsStderrFromChild);
  153.         
  154.         fclose(fpFromChild);
  155.         close(stdoutFromChild);
  156.         close(stderrFromChild);
  157.         fclose(fpToChild);
  158.         running = NO;
  159.         done = [delegate methodFor:@selector(subprocess:done::)];
  160.         done(delegate,@selector(subprocess:done::),self,status,code);
  161.     }
  162.     return self;
  163. }
  164.  
  165. - _stdoutBuffer
  166. {
  167.     return stdoutBuffer;
  168. }
  169.  
  170. - _stderrBuffer
  171. {
  172.     return stderrBuffer;
  173. }
  174.  
  175. /*
  176.  * DPS handler for output from subprocess
  177.  */
  178.  
  179. - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString
  180. { // re-written by Carl Lindberg to make it safer.
  181.     int  bufferCount, currleft = BUFSIZ / 2;
  182.     do {
  183.         int  currentLength = [aString length];
  184.         char buf[(currleft * 2) + 1];
  185.  
  186.         currleft *=2;
  187.         bufferCount = read(theFd, buf, currleft);
  188.         if (bufferCount <= 0) {
  189.             if (currentLength > 0)
  190.                 [self flushBuffer:aString];
  191.             
  192.             /*
  193.              * DPSAddFd() just happens to invoke the handler
  194.              * function even though there may be no data to
  195.              * read on this fd.  So don't blindly exit the
  196.              * first time read returns 0; wait until
  197.              * both stderr and stdout
  198.              * have closed before calling _childDidExit; otherwise
  199.              * you risk losing data if stderr happens to be
  200.              * done first.             
  201.              */
  202.             if ( theFd == stdoutFromChild )
  203.                  stdoutIsDone = YES;
  204.             else if ( theFd == stderrFromChild )
  205.                 stderrIsDone = YES;
  206.                 
  207.             if ( stdoutIsDone && stderrIsDone ) 
  208.                 [self _childDidExit];
  209.             return self;
  210.         }
  211.         buf[bufferCount] = 0;
  212.         [aString cat:buf];
  213.     } while (bufferCount == currleft);
  214.     [self flushBuffer:aString];
  215.     return self;
  216. }
  217.  
  218. /*
  219.  * DPS handler for output from subprocess
  220.  */
  221. static void stdoutFdHandler(int theFd,id self)
  222. {
  223.     [self _fdHandler:theFd method:@selector(subprocess:output:)
  224.             buffer:[self _stdoutBuffer]];
  225. }
  226.  
  227. static void stderrFdHandler(int theFd,id self)
  228. {
  229.     [self _fdHandler:theFd method:@selector(subprocess:stderrOutput:)
  230.             buffer:[self _stderrBuffer]];
  231. }
  232.  
  233. - _setRunning:(BOOL)yn
  234. {
  235.     running = yn;
  236.     return self;
  237. }
  238.  
  239. /*---------------------------< INIT/FREE METHODS >---------------------------*/
  240.  
  241. - init:(const char *)aString withDelegate:theDelegate
  242.         keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
  243.         asynchronously:(BOOL)async
  244. {
  245.     [super init];
  246.     usePtys = ptyFlag;
  247.     asynchronous = async;
  248.     stdoutBuffer = [[MiscString allocFromZone:[self zone]]
  249.         initCapacity:BUFSIZ];
  250.     stderrBuffer = [[MiscString allocFromZone:[self zone]] 
  251.         initCapacity:BUFSIZ];
  252.     environment = [[MiscStringArray allocFromZone:[self zone]] init];
  253.     dpsStdoutFromChild = dpsStderrFromChild = -1;    // DPSAddFd() not in use yet
  254.     if (flag) {
  255.         int i;
  256.         for (i=0; environ[i]; i++)
  257.             [environment addString:environ[i]];
  258.     }
  259.     [self setDelegate:theDelegate];
  260.     [self setExecArgs:"/bin/sh" :"sh" :"-c"];
  261.     if (aString)
  262.         [self execute:aString withPtys:usePtys asynchronously:asynchronous];
  263.     return self;
  264. }
  265.  
  266. - free
  267. {
  268.     free(execArgs[0]);
  269.     free(execArgs[1]);
  270.     free(execArgs[2]);
  271.     [self terminate:self];
  272.     [self _childDidExit];    // does DPSRemoveFd for us
  273.     [stdoutBuffer free];
  274.     [stderrBuffer free];
  275.     [environment free];
  276.     return [super free];
  277. }
  278.  
  279. /*-----------------------------< OTHER METHODS >-----------------------------*/
  280.  
  281. - execute:(const char *)aString withPtys:(BOOL)ptyFlag
  282.     asynchronously:(BOOL)async
  283. {
  284.     int pipeTo[2], pipeFrom[2], pipeStderr[2]; // for stderr to different fd
  285.     int tty, numFds, fd, processGroup;
  286.     char hail[BUFSIZ];
  287.  
  288.     if ([self isRunning]) return nil;
  289.     if (ptyFlag)
  290.     {
  291.           tty = open("/dev/tty", O_RDWR);
  292.       getptys(&masterPty,&slavePty);
  293.       if (masterPty <= 0 || slavePty <= 0 || pipe(pipeStderr) < 0) {
  294.         [delegate perform:@selector(subprocess:error:) with:self 
  295.               with:(void *)"Error grabbing ptys for subprocess."];
  296.         return self;
  297.       }
  298.       // remove the controlling tty if launched from a shell,
  299.       // but not Workspace;
  300.       // so that we have job control over the parent application in shell
  301.       // and so that subprocesses can be restarted in Workspace
  302.       if  ((tty<0) && ((tty = open("/dev/tty", 2))>=0)) {
  303.         ioctl(tty, TIOCNOTTY, 0);
  304.         close(tty);
  305.       }
  306.         }
  307.     else {
  308.       if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0) {
  309.           [delegate perform:@selector(subprocess:error:)
  310.                 with:self with:(void *)PIPE_ERROR];
  311.           return nil;
  312.         }
  313.     }
  314.     switch (childPid = vfork()) {
  315.         case -1:                            /* error */
  316.             [delegate perform:@selector(subprocess:error:)
  317.                     with:self with:(void *)VFORK_ERROR];
  318.             return self;
  319.             break;
  320.         case 0:                                /* child */
  321.             if (ptyFlag) {
  322.               dup2(slavePty, 0);
  323.               dup2(slavePty, 1);
  324.               dup2(pipeStderr[1], 2);  //stderr
  325.              }
  326.             else {
  327.               dup2(pipeTo[0], 0);
  328.               dup2(pipeFrom[1], 1);            /* get stdout from process */
  329.               dup2(pipeStderr[1], 2);            /* get stderr here */
  330.              }
  331.             numFds = getdtablesize();
  332.             for (fd = 3; fd < numFds; fd++) close(fd);
  333.             processGroup = getpid();
  334.             ioctl(0, TIOCSPGRP, (char *)&processGroup);
  335.             setpgrp(0, processGroup);
  336.             fgets(hail, BUFSIZ, stdin);        // wait for parent
  337.             [self execChild:aString];
  338.             [delegate perform:@selector(subprocess:error:)
  339.                     with:self with:(void *)EXEC_ERROR];
  340.             [self error:"vfork (child) returned!"];
  341.             break;
  342.         default:                            /* parent */
  343.             if (ptyFlag) {
  344.               close(slavePty);
  345.               close(pipeStderr[1]);
  346.               fpToChild = fdopen(masterPty,"w");
  347.               stdoutFromChild = masterPty;
  348.               stderrFromChild = pipeStderr[0];
  349.              }
  350.             else {
  351.               close(pipeTo[0]);
  352.               close(pipeFrom[1]);
  353.               close(pipeStderr[1]);
  354.               fpToChild = fdopen(pipeTo[1], "w");
  355.               stdoutFromChild = pipeFrom[0];
  356.               fpFromChild = fdopen(pipeFrom[0],"r");
  357.               stderrFromChild = pipeStderr[0];
  358.              }
  359.             // Set buffering method, also make it use its own buffers
  360.             setbuf(fpToChild, NULL);                    /* no buffering */
  361.             if (!ptyFlag) setbuf(fpFromChild, NULL);
  362.             if ( async ) {
  363.                 // Don't read output now;
  364.                 // Tell DPS to notify us when data is ready.
  365.                 stdoutIsDone = NO;
  366.                 DPSAddFD(dpsStdoutFromChild = stdoutFromChild,
  367.                         (DPSFDProc)stdoutFdHandler, self,
  368.                         NX_MODALRESPTHRESHOLD + 1);
  369.                 stderrIsDone = NO;
  370.                 DPSAddFD(dpsStderrFromChild = stderrFromChild,
  371.                         (DPSFDProc)stderrFdHandler, self,
  372.                         NX_MODALRESPTHRESHOLD + 1);
  373.                 running = YES;
  374.                 fputs("Run away!  Run away!\n", fpToChild); // tell child to go
  375.             } else {  //synchronous
  376.                 // from Steve Hayman for synch processes...
  377.                 // modified by Nicolas Droux and Carl Lindberg
  378.                 // Tell the subprocess to go right now, we'll read all its
  379.                 // output until end-of-file and return when the subprocess
  380.                 // has exited.  We still fork, but we won't return to the
  381.                 // main thread until the child exits...
  382.  
  383.                 fd_set readfds;
  384.                   int highestFdNum;
  385.  
  386.                 running = YES;                
  387.                   dpsStdoutFromChild = dpsStderrFromChild = -1;
  388.                 //Not using DPSAddFd()
  389.  
  390.                   highestFdNum = stdoutFromChild > stderrFromChild ?
  391.                                  stdoutFromChild : stderrFromChild;
  392.  
  393.                 stdoutIsDone = stderrIsDone = NO;
  394.                   fputs("Run away!  Run away!\n", fpToChild);
  395.                 do {
  396.                     // set up the set of fd's we are using
  397.                     FD_ZERO(&readfds);
  398.                     FD_SET(stdoutFromChild, &readfds);
  399.                     FD_SET(stderrFromChild, &readfds);
  400.  
  401.                     // select the ones that nead reading from
  402.                     select(highestFdNum+1,&readfds,NULL,NULL,NULL);
  403.  
  404.                     if (FD_ISSET(stdoutFromChild, &readfds))
  405.                              stdoutFdHandler(stdoutFromChild, self);
  406.                       if (FD_ISSET(stderrFromChild, &readfds))
  407.                         stderrFdHandler(stderrFromChild, self);
  408.  
  409.                 } while(!stdoutIsDone || !stderrIsDone);
  410.                 // The above condition is the same one that _fdHandler uses to
  411.                 // call _childDidExit, so that call has already been done by this point.
  412.             }
  413.             break;
  414.     }
  415.     return self;
  416. }
  417.  
  418. - setExecArgs:(const char *)a0 :(const char *)a1 :(const char *)a2
  419. {
  420.     int i;
  421.  
  422.     for (i=0; i<3; i++) if (execArgs[i]) free(execArgs[i]);
  423.     execArgs[0] = NXCopyStringBufferFromZone(a0, [self zone]);
  424.     execArgs[1] = NXCopyStringBufferFromZone(a1, [self zone]);
  425.     execArgs[2] = NXCopyStringBufferFromZone(a2, [self zone]);
  426.     return self;
  427. }
  428.  
  429. - execChild:(const char *)aString
  430. {
  431.     /*
  432.      * we exec a /bin/sh so that cmds are easier to specify for the user
  433.      * Unlike the old method, we don't automatically
  434.      * use "/bin/sh", "sh", "-c" anymore
  435.      */
  436.     execle( execArgs[0], execArgs[1], execArgs[2], aString, 0, 
  437.         [environment stringArray] );
  438. // old: execle("/bin/sh", "sh", "-c", aString, 0, [environment stringArray]);
  439.     return self;    // shouldn't get here
  440. }
  441.  
  442. - setDelegate:anObject
  443. {
  444.     delegate = anObject;
  445.     return self;
  446. }
  447.  
  448. - delegate
  449. {
  450.     return delegate;
  451. }
  452.  
  453. - environment
  454. {
  455.     return environment;
  456. }
  457.  
  458. - (SEL)outputMethodForBuffer:aBuffer
  459. {
  460.     SEL aSelector;
  461.     if (aBuffer == [self _stdoutBuffer])
  462.         aSelector = @selector(subprocess:output:);
  463.     else {
  464.         if (aBuffer == [self _stderrBuffer])
  465.             aSelector = @selector(subprocess:stderrOutput:);
  466.         else aSelector = NULL;
  467.     }
  468.     return aSelector;
  469. }
  470.  
  471. - flushBuffer:aString as:aBuffer
  472. {
  473.     if ([aString length] > 0) {
  474.         SEL aSelector = [self outputMethodForBuffer:aBuffer];
  475.         if (aSelector)
  476.             [delegate perform:aSelector
  477.                     with:self with:(id)[aString stringValue]];
  478.         [aString setStringValue:""];
  479.     }
  480.     return nil;
  481. }
  482.  
  483. - flushBuffer:aString
  484. {
  485.     return [self flushBuffer:aString as:aString];
  486. }
  487.  
  488. - send:(const char *)string withNewline:(BOOL)wantNewline
  489. {
  490.     fputs(string,fpToChild);
  491.     if (wantNewline)
  492.         fputc('\n',fpToChild);
  493.     return self;
  494. }
  495.  
  496. - send:(const char *)string
  497. {
  498.     [self send:string withNewline:YES];
  499.     return self;
  500. }
  501.  
  502. /*
  503.  * Returns the process id of the process (and therefore the process group
  504.  * of the job)
  505.  */
  506. - (int)pid
  507. {
  508.     return childPid;
  509. }
  510.  
  511. - pause:sender
  512. {
  513.     if (!paused) {
  514.         killpg(childPid,SIGSTOP);            /* pause the process group */
  515.         paused = YES;
  516.     }
  517.     return self;
  518. }
  519.  
  520. - resume:sender
  521. {
  522.     if (paused) {
  523.         killpg(childPid,SIGCONT);            /* resume the process group */
  524.         paused = NO;
  525.     }
  526.     return self;
  527. }
  528.  
  529. - (BOOL)isPaused
  530. {
  531.     return paused;
  532. }
  533.  
  534. - (BOOL)isRunning
  535. {
  536.     return running;
  537. }
  538.  
  539. - (BOOL)usePtys { return usePtys; }
  540. - setUsePtys:(BOOL)flag { usePtys = flag; return self; }
  541.  
  542. - (BOOL)asynchronous { return asynchronous; }
  543. - setAsynchronous:(BOOL)flag { asynchronous = flag; return self; }
  544.  
  545. - terminate:sender
  546. {
  547.     if (running)
  548.         killpg(childPid,SIGKILL);
  549.     return self;
  550. }
  551.  
  552. /*
  553.  * effectively sends an EOF to the child process stdin
  554.  */
  555. - terminateInput
  556. {
  557.     fclose(fpToChild);
  558.     return self;
  559. }
  560.  
  561. @end
  562.  
  563. @implementation Object(MiscSubprocessDelegate)
  564.  
  565. - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code
  566. {
  567.     return self;
  568. }
  569.  
  570. - subprocess:sender output:(const char *)buffer
  571. {
  572.     return self;
  573. }
  574.  
  575. - subprocess:sender stderrOutput:(const char *)buffer
  576. {
  577.     return self;
  578. }
  579.  
  580. - subprocess:sender error:(const char *)errorString
  581. {
  582.     perror(errorString);
  583.     return self;
  584. }
  585.  
  586. @end
  587.  
  588.  
  589. @implementation MiscSubprocess(Convenience)
  590.  
  591. // These were automatically generated using cnvwrap.
  592.  
  593. - init
  594. { return [self init:NULL withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:YES]; }
  595.  
  596. - init:(const char *)aString
  597. { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:YES]; }
  598.  
  599. - init:(const char *)aString withDelegate:theDelegate
  600. { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:NO asynchronously:YES]; }
  601.  
  602. - init:(const char *)aString keepEnvironment:(BOOL)flag
  603. { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:NO asynchronously:YES]; }
  604.  
  605. - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag
  606. { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:NO asynchronously:YES]; }
  607.  
  608. - init:(const char *)aString withPtys:(BOOL)ptyFlag
  609. { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:ptyFlag asynchronously:YES]; }
  610.  
  611. - init:(const char *)aString withDelegate:theDelegate withPtys:(BOOL)ptyFlag
  612. { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:ptyFlag asynchronously:YES]; }
  613.  
  614. - init:(const char *)aString keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
  615. { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:ptyFlag asynchronously:YES]; }
  616.  
  617. - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
  618. { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:ptyFlag asynchronously:YES]; }
  619.  
  620. - init:(const char *)aString asynchronously:(BOOL)async
  621. { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:async]; }
  622.  
  623. - init:(const char *)aString withDelegate:theDelegate asynchronously:(BOOL)async
  624. { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:NO asynchronously:async]; }
  625.  
  626. - init:(const char *)aString keepEnvironment:(BOOL)flag asynchronously:(BOOL)async
  627. { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:NO asynchronously:async]; }
  628.  
  629. - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag asynchronously:(BOOL)async
  630. { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:NO asynchronously:async]; }
  631.  
  632. - init:(const char *)aString withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async
  633. { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:ptyFlag asynchronously:async]; }
  634.  
  635. - init:(const char *)aString withDelegate:theDelegate withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async
  636. { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:ptyFlag asynchronously:async]; }
  637.  
  638. - init:(const char *)aString keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async
  639. { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:ptyFlag asynchronously:async]; }
  640.  
  641. - execute:(const char *)aString
  642. { return [self execute:aString withPtys:usePtys asynchronously:asynchronous]; }
  643.  
  644. - execute:(const char *)aString withPtys:(BOOL)ptyFlag
  645. { return [self execute:aString withPtys:ptyFlag asynchronously:asynchronous]; }
  646.  
  647. - execute:(const char *)aString asynchronously:(BOOL)async
  648. { return [self execute:aString withPtys:usePtys asynchronously:async]; }
  649.  
  650. @end